package constructdata.TestDataConstructors;

import codetree.CodeTree;
import codetree.CodeTreeOperation;
import codetree.TreeNode;

import java.io.*;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * Termination Test Data Construction
 * Created by wangxin on 13/07/2017.
 */
public class TerminationTestDataConstructor {
    // The marks
    private final String TERMMARK = "termination";

    // The maximum hole number of the continuous holes
    private int MAX_HOLENUM = 5;
    // The construct trees
    private LinkedList<CodeTree> trees;
    // The predication holes correspondent to the constructTrees;
    private LinkedList<String> predictions;
    // The predicate node's complete class name
    private LinkedList<String> classnames;
    // The node who is the parent of the predicated one
    private LinkedList<TreeNode> parents;
    // CodeTreeOperator
    private CodeTreeOperation operator;
    // The size of hole
    private  LinkedList<String> holesizes;
    // The block predictions
    private LinkedList<String> blockpredictions;
    private String blockprediction;
    // The original statements
    private LinkedList<String> originalStatements;
    // The variable names
    private LinkedList<String> variableNames;
    // The block original statements
    private LinkedList<String> blockstatements;
    private String blockstatement;

    // testcase trace
    private LinkedList<String> testcaseTraces;
    // testcase pool: <trace, testcase>
    private Map<String, String> testcasePool;
    private Map<Integer,String> sourceLines;
    private String trace;
    private String testcaseDir;

    /**
     * Constructor
     * */
    public TerminationTestDataConstructor(){
        operator = new CodeTreeOperation();
    }

    public void construct(CodeTree tree,
                          FileWriter treeWriter,FileWriter predictionWriter,FileWriter classWriter, FileWriter generationNodeWriter,FileWriter treeSentenceWriter, FileWriter jarWriter, FileWriter holeSizeWriter,
                          FileWriter traceWriter, // trace back
                          FileWriter blockpredictionWriter, // block of predictions (more lines)
                          FileWriter originalStatementsWriter, // original statements
                          FileWriter variableNamesWriter,// variable names
                          FileWriter linesWriter,// lines writer
                          FileWriter testCaseTraceWriter,
                          FileWriter blockOriginalStatementsWriter,
                          String testcaseDir,
                          boolean isCompleteFlag){
        this.testcaseDir = testcaseDir;

        LinkedList<LinkedList> result = getConstructTrainingData(tree);
        List<CodeTree> treeList = result.get(0);
        List<String> predictionList = result.get(1);
        List<String> classList = result.get(2);
        List<TreeNode> generationNodeList = result.get(3);
        List<String> holeSizeList = result.get(4);// record size of hole
        List<String> blockpredictionList = result.get(5);
        List<String> originalStatementsList = result.get(6);
        List<String> variableNameList = result.get(7);
        List<String> testCaseTraceList = result.get(8);
        List<String> blockoriginalList = result.get(9);

        //FindJarHandler findJarHandler = new FindJarHandler();
        for (int i = 0; i < treeList.size(); i++) {
            try {
                //String jar = findJarHandler.getPackage(classList.get(i));
                //if(jar != null) {
                CodeTree tempTree = treeList.get(i);
                operator.saveRegularizedTreeInFile(operator.regularization(tempTree), treeWriter);
                operator.saveTrainingPredictionInFile(predictionList.get(i), predictionWriter);
                operator.saveTrainingPredictionInFile(classList.get(i), classWriter);
                //operator.saveTrainingPredictionInFile(jar, jarWriter);
                int parentnum = generationNodeList.get(i).getSerialNumber();
                operator.saveTrainingPredictionInFile(parentnum + " " + ((parentnum != 0) ? generationNodeList.get(i).getCompleteMethodDeclaration() : ""), generationNodeWriter);
                operator.saveTreeStringFormatInFile(tempTree, treeSentenceWriter, isCompleteFlag);

                operator.saveTrainingPredictionInFile(holeSizeList.get(i), holeSizeWriter);

                operator.saveTrainingPredictionInFile(tree.getFunctionTrace(), traceWriter);
                operator.saveTrainingPredictionInFile(blockpredictionList.get(i), blockpredictionWriter);

                operator.saveTrainingPredictionInFile(originalStatementsList.get(i), originalStatementsWriter);
                operator.saveTrainingPredictionInFile(variableNameList.get(i), variableNamesWriter);

                operator.saveTrainingPredictionInFile((tempTree.getLines(tempTree.getRoot()) + 1)+"",linesWriter);

                operator.saveTrainingPredictionInFile( testCaseTraceList.get(i),testCaseTraceWriter);

                operator.saveTrainingPredictionInFile(blockoriginalList.get(i), blockOriginalStatementsWriter);
                //}
            } catch (Exception e) {
                System.err.println(e.getMessage());
            } catch (Error e){
                System.err.println(e.getMessage());
            }
        }
    }


    /**
     * Construct training tree from the input code tree
     * */
    public LinkedList<LinkedList> getConstructTrainingData(CodeTree completeTree){
        // Init
        trees = new LinkedList<>();
        predictions = new LinkedList<>();
        classnames = new LinkedList<>();
        parents = new LinkedList<>();
        holesizes = new LinkedList<>();
        blockpredictions = new LinkedList<>();

        originalStatements = new LinkedList<>();
        variableNames = new LinkedList<>();
        blockstatements = new LinkedList<>();

        testcaseTraces = new LinkedList<>();
        testcasePool = new HashMap<>(); // -- for test case
        sourceLines = new HashMap<>();

        int count = 1;
        for (int i = 1; i <= 1; i++) {// construct from the first node, construct once
            construct(completeTree, i);
        }

        // Return
        LinkedList<LinkedList> result = new LinkedList<>();
        result.addLast(trees);
        result.addLast(predictions);
        result.addLast(classnames);
        result.addLast(parents);
        result.addLast(holesizes);
        result.addLast(blockpredictions);
        result.addLast(originalStatements);
        result.addLast(variableNames);

        result.addLast(testcaseTraces);
        result.addLast(blockstatements);
        return result;
    }

    /**
     * @param completeTree: training data from this tree
     * @param serialNumber: node to construct termination mark with this serial number
     * */
    private void construct(CodeTree completeTree, int serialNumber){
        // Make the replica of the completeTree to avoid destroying.
        CodeTree tree = copyCodeTree(completeTree);
        operator.setSerialNumberofEachNode(tree);

        // Initialize the blockprediction
        blockprediction  = "";
        blockstatement = "";

        // The source code
        trace = Util.getTrace(completeTree);
        sourceLines = Util.getSourceLines(trace);

        if(tree.getTotalNumber() >= serialNumber){
            TreeNode node = tree.getTreeNode(serialNumber);
            predictions.addLast(TERMMARK);
            trees.addLast(tree);
            classnames.addLast(TERMMARK);
            parents.addLast(node);
            holesizes.addLast("" + 0);
            blockpredictions.addLast(blockprediction);
            originalStatements.addLast(node.getStatement());
            blockstatements.addLast(blockstatement);

            List<String> previousVariableNames = node.getPreviousVariableNames();
            String variablename = previousVariableNames.size()>0?previousVariableNames.get(0):"";
            for (int i = 1; i < previousVariableNames.size(); i++) {
                variablename += " " + previousVariableNames.get(i);
            }
            variableNames.addLast(variablename);

            String testcase = Util.convert2SourceCode(sourceLines);
            // extract the function
            String methodinfo = tree.getMethodInfo();
            int b = Integer.parseInt(methodinfo.split(" ")[0]);
            int e = Integer.parseInt(methodinfo.split(" ")[1]);
            String functionblock = Util.convert2SourceCode(sourceLines, b, e);

            // construct the testcase: testcase = sourceinfo + functionblock + }
            String sourceinfo = tree.getSourceInfo();
            testcase = sourceinfo + functionblock + "}";

            // test case save
            String testcaseTrace = saveTestCase(testcase, 1, trace, b + "to" + e, "Termination");
            testcasePool.put(testcaseTrace,testcase);
            // test case trace
            testcaseTraces.addLast(testcaseTrace);
        }
    }

    private CodeTree copyCodeTree(CodeTree completeTree) {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(completeTree);
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            CodeTree tree = (CodeTree) ois.readObject();
            return tree;
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    private String saveTestCase(String testcase, int count, String trace, String lines, String flag) {
        String path = testcaseDir + trace.replace("/", "_")+"_"+lines+"_"+count+"_"+flag+".java";
        try {
            BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(path));
            bufferedWriter.write(testcase);
            bufferedWriter.flush();
            bufferedWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return path;
    }
}
